package xyz.lwm.lazycat.vertx;

import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import xyz.lwm.lazycat.utility.Assert;
import xyz.lwm.lazycat.utility.It;
import xyz.lwm.lazycat.utility.PathUtil;
import xyz.lwm.lazycat.utility.StringUtil;
import xyz.lwm.lazycat.utility.ThrowableUtil;
import xyz.lwm.lazycat.web.WebException;
import xyz.lwm.lazycat.web.WebServer;
import xyz.lwm.lazycat.web.handle.Context;
import xyz.lwm.lazycat.web.util.ContextHolder;
import xyz.lwm.lazycat.web.handle.HandlerType;
import xyz.lwm.lazycat.web.handle.JsonMapper;
import xyz.lwm.lazycat.web.route.Route;
import xyz.lwm.lazycat.web.route.RouteTable;
import xyz.lwm.lazycat.web.settings.FileUploadSetting;
import xyz.lwm.lazycat.web.settings.ThreadPoolSetting;
import xyz.lwm.lazycat.web.settings.WebSetting;

import static xyz.lwm.lazycat.utility.StrPool.STR_SLASH;
import static xyz.lwm.lazycat.vertx.VertxConstants.DEFAULT_PORT;
import static xyz.lwm.lazycat.vertx.VertxConstants.MAX_POOL_SIZE;
import static xyz.lwm.lazycat.vertx.VertxConstants.MEMORY_SIZE;
import static xyz.lwm.lazycat.vertx.VertxConstants.TMP_UPLOAD_PATH;

/**
 * vertx http server
 *
 * @author lwm
 */
public class VertxHttpServer implements WebServer {

    private final WebSetting webProperties;

    private final JsonMapper jsonMapper;

    private HttpServer httpServer;

    public VertxHttpServer(WebSetting webProperties, JsonMapper jsonMapper) {
        this.webProperties = webProperties;
        this.jsonMapper = jsonMapper;
    }

    @Override
    public void start(RouteTable routeTable) {
        // vertx
        VertxOptions options = new VertxOptions();
        It.of(webProperties.getServer().getThreadPool())
                .let(ThreadPoolSetting::getMaxPoolSize)
                .filter(i -> i >= MAX_POOL_SIZE).orElse(MAX_POOL_SIZE)
                .also(options::setMaxWorkerExecuteTime);
        Vertx vertx = Vertx.vertx(options);

        // router
        Router router = Router.router(vertx);

        // context path 处理
        String contextPath = webProperties.getServer().getContextPath();
        String prefix = STR_SLASH + PathUtil.normalizePath(contextPath);

        // body handler
        BodyHandler bodyHandler = BodyHandler.create().setMergeFormAttributes(true).setDeleteUploadedFilesOnEnd(true);
        It<FileUploadSetting> uploadIt = It.of(webProperties.getServer().getFileUpload());
        uploadIt.let(FileUploadSetting::getMemorySize).filter(i -> i >= MEMORY_SIZE)
                .orElse(MEMORY_SIZE).also(bodyHandler::setBodyLimit);
        uploadIt.let(FileUploadSetting::getTmpUploadPath).filter(StringUtil::isNotBlank)
                .orElse(TMP_UPLOAD_PATH).also(bodyHandler::setUploadsDirectory);

        router.route("/*").handler(bodyHandler).handler(ctx -> {
            VertxHttpRequest request = new VertxHttpRequest(ctx);
            VertxHttpResponse response = new VertxHttpResponse(ctx);
            Context context = new Context(request, response, jsonMapper);

            ContextHolder.set(context);

            try {
                String path = request.getPath();
                HandlerType handlerType = getHandlerType(request.getMethod());

                if (STR_SLASH.equals(path)) {
                    It.of(routeTable.get(handlerType, path))
                            .getOrThrow(() -> new WebException("not found route: " + path))
                            .getHandler().handle(context);

                } else if (path.startsWith(prefix)) {
                    // 获取请求路径
                    final String reqPath = path.substring(prefix.length());
                    Route route = routeTable.get(handlerType, reqPath);
                    Assert.isTrue(route != null, "not found route: {} {}", handlerType, reqPath);
                    // 处理路径参数
                    request.getPathParams().putAll(VertxParamUtil.parsePathParams(route.getPath(), reqPath));
                    // 处理请求
                    route.getHandler().handle(context);

                } else {
                    context.sendError(404, "not found");
                }

                ctx.response().end();

            } catch (Exception e) {
                context.json(ThrowableUtil.print(e));

            } finally {
                ContextHolder.clear();
            }
        });

        int port = It.of(webProperties.getServer().getPort()).filter(p -> p > 0)
                .orElse(DEFAULT_PORT).also(webProperties.getServer()::setPort).get();

        httpServer = vertx.createHttpServer().requestHandler(router).listen(port).result();
    }

    private HandlerType getHandlerType(String requestMethod) {
        try {
            return HandlerType.valueOf(requestMethod.toUpperCase());
        } catch (Exception e) {
            throw new WebException(requestMethod + " not support", e);
        }
    }

    @Override
    public void stop() {
        if (httpServer != null) {
            httpServer.close();
        }
    }

}
